﻿using System;
using System.Collections.Generic;
using System.Xml.Linq;
using log4net;
using Sherwood.Configuration;
using Sherwood.Content.Contracts;
using Sherwood.Content.SearchClient;

namespace Sherwood.Content.UpdateScheduler
{
    public static class RecordPuller
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(RecordPuller));

        public static void UpdateAll(UpdateContext context, client client)
        {
            if(Log.IsInfoEnabled)
                Log.InfoFormat("Updating all records from client {0}", client.id);

            Update(client, source => source.GetAllRecords(context));
        }

        public static void UpdateModifiedSince(UpdateContext context, client client, DateTime firstModificationDateUtc)
        {
            if (Log.IsInfoEnabled)
                Log.InfoFormat("Updating all records changed since {1} (UTC) from client {0}", client.id, firstModificationDateUtc);

            Update(client, source => source.GetRecordsModifiedSince(context, firstModificationDateUtc));
        }

        private static void Update(client client, Func<IRecordPullSource, IEnumerable<string>> getRecordsHandler)
        {
            IRecordPullSource source;
            updateRecordsRequest request;
            IXmlUpdateClient xmlUpdateClient;
            Initialize(client, out source, out request, out xmlUpdateClient);

            int counter = 0;
            foreach (var recordXml in getRecordsHandler(source))
            {
                if (++counter % client.batchSize.GetValueOrDefault(20) == 0)
                {
                    Submit(client, request, xmlUpdateClient);
                    request.content.Untyped.RemoveAll();
                }
                var recordElement = XElement.Parse(recordXml);
                request.content.Untyped.Add(recordElement);
            }

            if (counter > 0)
                Submit(client, request, xmlUpdateClient);
        }

        private static void Submit(client client, updateRecordsRequest request, IXmlUpdateClient xmlUpdateClient)
        {
            if(Log.IsDebugEnabled)
                Log.Debug("Submitting records to content server");
            try
            {
                request.Sign(client.privateKey);
                xmlUpdateClient.UpdateRecords(request);
            }
            catch(Exception ex)
            {
                using(var reader = request.Untyped.CreateReader())
                {
                    if (reader.Read())
                        Log.Error("Failed to submit request: " + reader.ReadOuterXml(), ex);
                }
            }
        }

        public static void PullDeletions(UpdateContext context, client client)
        {
            IRecordPullSource source;
            updateRecordsRequest request;
            IXmlUpdateClient xmlUpdateClient;
            Initialize(client, out source, out request, out xmlUpdateClient);

            var existingRecordsProvider = DependencyInjection.Resolve<IExistingRecordsSourceProvider>();
            var existingRecords = existingRecordsProvider.GetForClient(client.id, client.privateKey);

            foreach (var records in existingRecords.GetRecordPages())
            {
                request.content.record.Clear();
                foreach (var record in source.GetDeletedRecords(records))
                    request.content.record.Add(new record {id = record.RecordId, updateMode = "Delete"});

                if (request.content.record.Count > 0)
                    Submit(client, request, xmlUpdateClient);
            }
        }

        private static void Initialize(client client, out IRecordPullSource source, out updateRecordsRequest request, out IXmlUpdateClient xmlUpdateClient)
        {
            var type = Type.GetType(client.sourceType, true);
            source = (IRecordPullSource)DependencyInjection.Container.Resolve(type, null);

            if (source is IXmlConfigurable)
            {
                using (var configReader = client.configuration.Untyped.CreateReader())
                    ((IXmlConfigurable)source).Configure(configReader);
            }

            xmlUpdateClient = DependencyInjection.Resolve<IXmlUpdateClient>();

            request = new updateRecordsRequest
                          {
                              client = new Contracts.client { id = client.id },
                              content = new updateRecordsRequestType.contentLocalType()
                          };
        }
    }
}
